package com.zuokun.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zuokun.dispatcher.ProtocolDispatcher;
import com.zuokun.manager.PlayerManager;
import com.zuokun.protol.MsgBase;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.ChannelGroupFuture;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
 * 客户端消息处理类
 */
@Slf4j
@Data
public class ServerHandler extends ChannelInboundHandlerAdapter {

    //全部客户端连接
    public static final ChannelGroup allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    /**
     * 读取客户端传来的消息
     *
     * @param ctx
     * @param msg
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf buffer = (ByteBuf) msg;
        while (buffer.readableBytes() >= 2) {
            //保存当前读取位置于
            buffer.markReaderIndex();

            /**
             * 获取消息体整体长度
             * 由于客户端是用小端方式传过来的长度，所以要先把高位向左移动8位，再执行或运算将它们合并成一个int型
             */
            int totalLen = (buffer.readUnsignedByte()) | (buffer.readUnsignedByte() << 8);

            //判断消息体是否完整
            if (buffer.readableBytes() < totalLen) {
                //把 readerIndex 恢复到上次保存的位置
                buffer.resetReaderIndex();
                return;
            }

            // 协议名长度
            int protoNameLen = (buffer.readUnsignedByte()) | (buffer.readUnsignedByte() << 8);
            if (protoNameLen > totalLen - 2) {
                log.warn("协议名长度非法");
                return;
            }

            // 获取协议名
            byte[] protoNameBytes = new byte[protoNameLen];
            buffer.readBytes(protoNameBytes);
            String protoName = new String(protoNameBytes, StandardCharsets.UTF_8);

            // 消息体
            int bodyLen = totalLen - 2 - protoNameLen;
            byte[] bodyBytes = new byte[bodyLen];
            buffer.readBytes(bodyBytes);
            String message = new String(bodyBytes, StandardCharsets.UTF_8);
            log.info("收到客户端消息：{} | {}", protoName, message);

            //返回消息
            try {
                Object protocol = ProtocolDispatcher.getProtocol(protoName);
                ObjectMapper mapper = new ObjectMapper();
                MsgBase msgBase = (MsgBase)mapper.readValue(message,protocol.getClass());
                msgBase.setCtx(ctx);
                if(PlayerManager.playerMap.containsKey(ctx)){
                    msgBase.setPlayerInfo(PlayerManager.playerMap.get(ctx));
                }
                byte[] result = ProtocolDispatcher.dispatch(protoName,msgBase);
                if(result != null && result.length > 4){
                    int bodyLength = result[0] | result[1] << 8;
                    int protoNameLength = result[2] | result[3] << 8;
                    byte[] body = Arrays.copyOfRange(result, 4, result.length);
                    switch (msgBase.getSendType()){
                        case Unicast:
                            ChannelFuture future = ctx.writeAndFlush(Unpooled.wrappedBuffer(result));
                            future.addListener(f -> {
                                if (f.isSuccess()) {
                                    log.info("成功向客户端发送消息:{} | {},{},{}", protoName, bodyLength,protoNameLength,new String(body, StandardCharsets.UTF_8));
                                } else {
                                    log.error("数据发送失败：" + f.cause());
                                }
                            });
                            break;
                        case BroadCast:
                            ChannelGroupFuture groupFuture = allChannels.writeAndFlush(Unpooled.wrappedBuffer(result));
                            groupFuture.addListener(f -> {
                                if (f.isSuccess()) {
                                    log.info("成功向全体客户端发送消息:{} | {},{},{}", protoName, bodyLength,protoNameLength,new String(body, StandardCharsets.UTF_8));
                                } else {
                                    log.error("广播数据发送失败：" + f.cause());
                                }
                            });
                            break;
                    }

                }
            } catch (Exception e) {
                log.error("channelRead:" + e.toString());
            }
        }
    }

    /**
     * 客户端连接异常处理
     *
     * @param ctx
     * @param cause
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

    /**
     * 将新客户端连接加入全局列表
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIP = socketAddress.getAddress().getHostAddress();
        int clientPort = socketAddress.getPort();
        log.info("新的客户端连接 IP：{}, 端口： {}", clientIP, clientPort);
        //新连接加入全局列表
        allChannels.add(ctx.channel());
        //调用父类方法，确保父类行为不丢失
        super.channelActive(ctx);
    }

    /**
     * 将断开的连接移除全局列表
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIP = socketAddress.getAddress().getHostAddress();
        int clientPort = socketAddress.getPort();
        log.info("客户端连接 IP：{}, 端口： {} 已断开", clientIP, clientPort);
        // 连接断开从列表移除
        allChannels.remove(ctx.channel());
        PlayerManager.playerMap.remove(ctx);
        //调用父类方法，确保父类行为不丢失
        super.channelInactive(ctx);
    }
}
